package com.angle3d;

import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;

import android.opengl.GLES20;

/**
 * Class representing a shader program.
 * @author Alessandro Boccalatte
 *
 */
public class ShaderProgram {

	private List<ShaderSource> mShaders = new ArrayList<ShaderSource>(4);
	private int mProgramId = -1;
	
	/**
	 * Creates a shader program for a given set of {@link ShaderSource} objects.
	 * In the typical case, a vertex shader and a fragment shader are expected.
	 * @param shaders the shaders linked by this vertex program.
	 */
	public ShaderProgram(ShaderSource ...shaders) {
		for (ShaderSource sh : shaders) {
			mShaders.add(sh);
		}
	}
	
	/**
	 * Returns the ID used by OpenGL to identify this shader program.
	 * @return the ID used by OpenGL to identify this shader program.
	 */
	public int getProgramId() {
		return mProgramId;
	}
	
	/**
	 * Applies this shader program.
	 */
	public void bind() {
		if (mProgramId < 0) {
			initProgram();
		}
		
		GLES20.glUseProgram(mProgramId);
	}
	
	private void initProgram() {
		mProgramId = GLES20.glCreateProgram();
		//compile shaders
		for (ShaderSource sh : mShaders) {
			int shader = loadShader(sh.getSource(), getShaderTypeAsGLInt(sh.getType()));
			if (shader >= 0) {
				GLES20.glAttachShader(mProgramId, shader);
			}
		}
		//link program
        GLES20.glLinkProgram(mProgramId);
	}
	
	private static int getShaderTypeAsGLInt(ShaderSource.ShaderType shaderType) {
		switch(shaderType) {
			case VERTEX: return GLES20.GL_VERTEX_SHADER;
			case FRAGMENT: return GLES20.GL_FRAGMENT_SHADER; 
			case GEOMETRY: return GLES20.GL_NONE;//not supported yet (maybe in future?)
		}
		return GLES20.GL_NONE;
	}
	
	private int loadShader(String shaderSrc, int type){
        int shader = GLES20.glCreateShader(type); 
        GLES20.glShaderSource(shader, shaderSrc);
        GLES20.glCompileShader(shader);
        return shader;
    }
	
	/**
	 * Sets the value of a uniform float parameter in the shader.
	 * @param paramName the parameter name, as it appears in the shader source code.
	 * @param value the value
	 */
	public void setUniformValue(String paramName, float value) {
		int loc = GLES20.glGetUniformLocation(mProgramId, paramName);
		if (loc >= 0) GLES20.glUniform1f(loc, value);
	}

	/**
	 * Sets the value of a uniform integer parameter in the shader.
	 * @param paramName the parameter name, as it appears in the shader source code.
	 * @param value the value
	 */
	public void setUniformValue(String paramName, int value) {
		int loc = GLES20.glGetUniformLocation(mProgramId, paramName);
		if (loc >= 0) GLES20.glUniform1i(loc, value);
	}

	/**
	 * Sets the value of a uniform vec2 parameter in the shader.
	 * @param paramName the parameter name, as it appears in the shader source code.
	 * @param value the value
	 */
	public void setUniformValue(String paramName, float v0, float v1) {
		int loc = GLES20.glGetUniformLocation(mProgramId, paramName);
		if (loc >= 0) GLES20.glUniform2f(loc, v0, v1);
	}

	/**
	 * Sets the value of a uniform vec2i parameter in the shader.
	 * @param paramName the parameter name, as it appears in the shader source code.
	 * @param value the value
	 */
	public void setUniformValue(String paramName, int v0, int v1) {
		int loc = GLES20.glGetUniformLocation(mProgramId, paramName);
		if (loc >= 0) GLES20.glUniform2i(loc, v0, v1);
	}

	/**
	 * Sets the value of a uniform vec3 parameter in the shader.
	 * @param paramName the parameter name, as it appears in the shader source code.
	 * @param value the value
	 */
	public void setUniformValue(String paramName, float v0, float v1, float v2) {
		int loc = GLES20.glGetUniformLocation(mProgramId, paramName);
		if (loc >= 0) GLES20.glUniform3f(loc, v0, v1, v2);
	}

	/**
	 * Sets the value of a uniform vec3i parameter in the shader.
	 * @param paramName the parameter name, as it appears in the shader source code.
	 * @param value the value
	 */
	public void setUniformValue(String paramName, int v0, int v1, int v2) {
		int loc = GLES20.glGetUniformLocation(mProgramId, paramName);
		if (loc >= 0) GLES20.glUniform3i(loc, v0, v1, v2);
	}

	/**
	 * Sets the value of a uniform vec4 parameter in the shader.
	 * @param paramName the parameter name, as it appears in the shader source code.
	 * @param value the value
	 */
	public void setUniformValue(String paramName, float v0, float v1, float v2, float v3) {
		int loc = GLES20.glGetUniformLocation(mProgramId, paramName);
		if (loc >= 0) GLES20.glUniform4f(loc, v0, v1, v2, v3);
	}

	/**
	 * Sets the value of a uniform vec4i parameter in the shader.
	 * @param paramName the parameter name, as it appears in the shader source code.
	 * @param value the value
	 */
	public void setUniformValue(String paramName, int v0, int v1, int v2, int v3) {
		int loc = GLES20.glGetUniformLocation(mProgramId, paramName);
		if (loc >= 0) GLES20.glUniform4i(loc, v0, v1, v2, v3);
	}

	/**
	 * Sets the value of a uniform mat4 parameter in the shader.
	 * @param paramName the parameter name, as it appears in the shader source code.
	 * @param value the value
	 */
	public void setUniformValue(String paramName, Matrix4 matrix) {
		int loc = GLES20.glGetUniformLocation(mProgramId, paramName);
		if (loc >= 0) GLES20.glUniformMatrix4fv(loc, 1, false, FloatBuffer.wrap(matrix.getValues()));
	}
}
